package org.gw.ylc.base.data;

import org.apache.commons.collections.MapUtils;
import org.gw.ylc.base.context.RequestContextHolder;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.*;

@Service
@SuppressWarnings("unchecked")
public class AuthorisedRepository implements InitializingBean {

	@PersistenceContext
	private EntityManager entityManager;

	@Transactional
	public <T> int deleteById(Class<T> type, String[] id) {
		StringBuilder buf = new StringBuilder("delete from ");
		buf.append(type.getName());
		buf.append(" t ");
		ClassMetadata metadata = allClassMetadata.get(type.getName());
		buf.append("where t.").append(metadata.getIdentifierPropertyName()).append(" in :id ");
		Query query = entityManager.createQuery(buf.toString());
		query.setParameter("id", Arrays.asList(id));
		query.executeUpdate();
		int i = query.getFirstResult();
		return i;
	}

	public <T> T find(Class<T> type, Object id) {
		ClassMetadata metadata = allClassMetadata.get(type.getName());
		System.out.println(metadata.getIdentifierPropertyName());
		return entityManager.find(type, id);
	}

	@Transactional
	public <T> T save(T entity) {
		entityManager.persist(entity);
		return entity;
	}

	@Transactional
	public <T> T update(T entity) {
		return entityManager.merge(entity);
	}

	@Transactional
	public <T> void remove(T entity) {
		entityManager.remove(entityManager.contains(entity) ? entity : entityManager.merge(entity));
	}

	public List<?> query(String qlString, Object... args) {
		Query query = entityManager.createQuery(qlString);
		for (int i = 0; i < args.length; i++) {
			query.setParameter(i + 1, args[i]);
		}
		return query.getResultList();
	}

	public <T> T findOne(String qlString, Object... args) {
		List<?> list = query(qlString, args);
		return (T) (list.size() > 0 ? list.get(0) : null);
	}

	public <T> Page<T> page(String qlString, Object... args) {
		return page(qlString, RequestContextHolder.get().getPageable(), args);
	}

	public <T> Page<T> page(String qlString, Pageable pageable, Object... args) {
		Query query = entityManager.createQuery(qlString);
		for (int i = 0; i < args.length; i++) {
			query.setParameter(i + 1, args[i]);
		}
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());
		query.setMaxResults(pageable.getPageSize());
		String countQuery = QueryUtils.createCountQueryFor(qlString);
		Query countQ = entityManager.createQuery(countQuery);
		for (int i = 0; i < args.length; i++) {
			countQ.setParameter(i + 1, args[i]);
		}
		List<Long> totals = countQ.getResultList();
		Long total = 0L;
		for (Long element : totals) {
			total += element == null ? 0 : element;
		}
		List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T> emptyList();
		return new PageImpl<T>(content, pageable, total);
	}

	private Map<String, ClassMetadata> allClassMetadata = new HashMap<String, ClassMetadata>();
	private Map<String, ClassMetadata> simpleNameMetadata = new HashMap<String, ClassMetadata>();

	public ClassMetadata getMetadata(String entity) {
		ClassMetadata metadata = allClassMetadata.get(entity);
		if (metadata == null) {
			metadata = simpleNameMetadata.get(entity);
		}
		return metadata;
	}

	public Map<String, ClassMetadata> getAllMetadata() {
		return MapUtils.unmodifiableMap(allClassMetadata);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Session session = (Session) entityManager.getDelegate();
		SessionFactory sessionFactory = session.getSessionFactory();
		allClassMetadata.putAll(sessionFactory.getAllClassMetadata());
		Collection<ClassMetadata> values = allClassMetadata.values();
		simpleNameMetadata.clear();
		for (ClassMetadata md : values) {
			simpleNameMetadata.put(md.getMappedClass().getSimpleName(), md);
		}
		// EmptyInterceptor QueryTranslator
		// org.hibernate.resource.jdbc.spi.StatementInspector
	}

}
